﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using MongoDB.Driver;

namespace Sustainalytics.DataLayer.Shared
{

    /// <summary>
    /// Creates a custom typesafe ordering structure used in Repositories as parameters
    /// </summary>
    /// <typeparam name="T">The type of the document.</typeparam>
    public class Ordering<T>
    {

        private readonly List<OrderingItem> _transforms;

   

        private Ordering(Expression<Func<T, object>> transform, bool direction = true)
        {
            _transforms = new List<OrderingItem> { new OrderingItem(transform, direction) };
        }

        internal List<OrderingItem> Transforms
        {
            get { return _transforms; }
        }

        /// <summary>
        ///  First key to be sorted by in ascending order.
        /// </summary>
        /// <param name="primary">sorting expression</param>
        /// <returns>a new ordering object</returns>
        public static Ordering<T> Ascending(Expression<Func<T, object>> primary)
        {
            if (primary == null)
            {
                throw new ArgumentNullException("primary");
            }
            return Create(primary, true);
        }

        /// <summary>
        ///  First key to be sorted by in descending order.
        /// </summary>
        /// <param name="primary">sorting expression</param>
        /// <returns>a new ordering object</returns>
        public static Ordering<T> Descending(Expression<Func<T, object>> primary)
        {
            if (primary == null)
            {
                throw new ArgumentNullException("primary");
            }
            return Create(primary, false);
        }

        /// <summary>
        ///  Adds key to be sorted by in ascending order.
        /// </summary>
        /// <param name="expression">sorting expression</param>
        /// <returns>a new ordering object</returns>
        public Ordering<T> ThenByAscending(Expression<Func<T, object>> expression)
        {
            if (expression == null)
            {
                throw new ArgumentNullException("expression");
            }
            Add(expression, true);
            return this;
        }

        /// <summary>
        ///  Adds key to be sorted by in descending order.
        /// </summary>
        /// <param name="expression">sorting expression</param>
        /// <returns>a new ordering object</returns>
        public Ordering<T> ThenByDescending(Expression<Func<T, object>> expression)
        {
            if (expression == null)
            {
                throw new ArgumentNullException("expression");
            }
            Add(expression, false);
            return this;
        }


     

        /// <summary>
        /// Creates a custom ordering structure used in Repositories as parameters
        /// </summary>
        /// <param name="primary">The first expression</param>
        /// <param name="direction">True for ascending and false for descending.</param>
        /// <returns></returns>
        private static Ordering<T> Create
            (Expression<Func<T, object>> primary, bool direction)
        {
            return new Ordering<T>(primary, direction);
        }

        private void Add(Expression<Func<T, object>> primary, bool direction)
        {
            _transforms.Add(new OrderingItem(primary, direction));
        }

        internal class OrderingItem
        {
            private readonly bool _direction;
            private readonly Expression<Func<T, object>> _transform;
            public OrderingItem(Expression<Func<T, object>> transform, bool direction = true)
            {
                _transform = transform;

                _direction = direction;
            }

            public bool Direction
            {
                get { return _direction; }
            }

            public Expression<Func<T, object>> Transform
            {
                get { return _transform; }
            }
        }
    }

    public static class OrderingUtils
    {
        /// <summary>
        /// Generates a Mongo sortby expression
        /// </summary>
        /// <returns></returns>
        public static SortDefinition<T> ToMongoSortBy<T>(this Ordering<T> ordering )
        {
            var sortDefinitions = new List<SortDefinition<T>>();

            var ascending = ordering.Transforms.Where(x => x.Direction);
            var descending = ordering.Transforms.Where(x => !x.Direction);

            foreach (var order in ascending)
            {
                sortDefinitions.Add(Builders<T>.Sort.Ascending(order.Transform));
            }

            foreach (var order in descending)
            {
                sortDefinitions.Add(Builders<T>.Sort.Descending(order.Transform));
            }

            return Builders<T>.Sort.Combine(sortDefinitions);
        }

        public static SortDefinition<T> ToMongoSortBy<T>(this string[] orderBy, bool ascending = true)
        {
            var sortDefinitions = new List<SortDefinition<T>>();

            foreach (var order in orderBy)
            {
                if (ascending)
                    sortDefinitions.Add(Builders<T>.Sort.Ascending(order));
                else
                    sortDefinitions.Add(Builders<T>.Sort.Descending(order));
            }

            return Builders<T>.Sort.Combine(sortDefinitions);
        }
    }

}

